﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows.Controls;
using System.Xml.Linq;

namespace DynamicGeometry
{
    public class DrawingDeserializer
    {
        public static Drawing OpenDrawing(Canvas canvas, XElement element)
        {
            Drawing drawing = new Drawing(canvas);
            ReadDrawing(drawing, element);
            return drawing;
        }

        public static Drawing OpenDrawing(Canvas canvas, string savedDrawing)
        {
            XElement element = XElement.Parse(savedDrawing);
            return OpenDrawing(canvas, element);
        }

        public static void ReadDrawing(Drawing drawing, XElement element)
        {
            var figuresNode = element.Element("Figures");
            var figures = ReadFigures(figuresNode, drawing);
            drawing.Add(figures);
            drawing.Recalculate();
            drawing.CoordinateSystem.MoveTo(drawing.Figures.OfType<IPoint>().Midpoint().Minus());
        }

        private static IEnumerable<IFigure> ReadFigures(XElement figuresNode, Drawing drawing)
        {
            Dictionary<string, IFigure> figures = new Dictionary<string, IFigure>();
            foreach (var figureNode in figuresNode.Elements())
            {
                var figure = ReadFigure(figureNode, figures, drawing);
                if (figure != null)
                {
                    yield return figure;
                }
            }
        }

        private static Dictionary<string, Type> mFigureTypes;
        public static Dictionary<string, Type> FigureTypes
        {
            get
            {
                if (mFigureTypes == null)
                {
                    mFigureTypes = new Dictionary<string, Type>();
                    foreach (var type in Assembly
                        .GetExecutingAssembly()
                        .GetTypes()
                        .Where(t => typeof(IFigure).IsAssignableFrom(t)))
                    {
                        mFigureTypes.Add(type.Name, type);
                    }
                }
                return mFigureTypes;
            }
        }

        public static Type FindType(string typeName)
        {
            return FigureTypes[typeName];
        }

        private static IFigure ReadFigure(
            XElement figureNode,
            Dictionary<string, IFigure> dictionary,
            Drawing drawing)
        {
            Type type = FindType(figureNode.Name.LocalName);
            if (type == null)
            {
                return null;
            }

            IFigure instance = null;
            IFigureList dependencies = ReadDependencies(figureNode, dictionary);

            var defaultCtor = type.GetConstructor(Type.EmptyTypes);
            if (defaultCtor != null)
            {
                instance = Activator.CreateInstance(type) as IFigure;
                instance.Dependencies = dependencies;
                instance.Drawing = drawing;
            }
            else
            {
                var ctorWithDependencies = type.GetConstructor(new Type[] { typeof(IFigureList) });
                if (ctorWithDependencies != null)
                {
                    instance = Activator.CreateInstance(type, dependencies) as IFigure;
                }
                else
                {
                    var ctorWithDrawingAndDependencies = type.GetConstructor(new Type[] { typeof(Drawing), typeof(IFigureList) });
                    if (ctorWithDrawingAndDependencies != null)
                    {
                        instance = Activator.CreateInstance(type, drawing, dependencies) as IFigure;
                    }
                }
            }

            instance.Name = figureNode.ReadString("Name");
            instance.ReadXml(figureNode);
            dictionary.Add(instance.Name, instance);

            return instance;
        }

        private static IFigureList ReadDependencies(XElement figureNode, Dictionary<string, IFigure> dictionary)
        {
            FigureList result = new FigureList();
            foreach (var node in figureNode.Elements("Dependency"))
            {
                var name = node.ReadString("Name");
                IFigure figure = null;
                if (dictionary.TryGetValue(name, out figure))
                {
                    result.Add(figure);
                }
            }
            return result;
        }

        public static void ReadDrawing(Drawing drawing, string savedDrawing)
        {
            XElement element = XElement.Parse(savedDrawing);
            ReadDrawing(drawing, element);
        }
    }
}
